package com.itxh.bean;

import com.itxh.bean.model.BeanDefinition;
import com.itxh.bean.model.PropertyValue;
import com.itxh.bean.model.RuntimeBeanReference;
import com.itxh.bean.model.TypedStringValue;
import com.itxh.factory.BeanFactory;
import com.itxh.factory.DefaultListableBeanFactory;
import com.itxh.reader.XmlBeanDefinitionReader;
import com.itxh.service.UserService;
import com.itxh.service.UserServiceImpl;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Demo {
    private BeanFactory beanFactory;

    @Test
    public void testIoC() {
//        UserService userService = new UserServiceImpl();
//        UserService userService = getUserService();
//        UserService userService = (UserService) getBean("userService");

        UserService userService = (UserService) beanFactory.getBean("userService");
        userService.saveUser();
    }

    public  UserService getUserService(){
        UserService userService = new UserServiceImpl();
        return userService;
    }

    public  Object getObject(String name){
        if ("userService".equals(name)){
            return new UserServiceImpl();
        }else if ("orderService".equals(name)){
            return new UserServiceImpl();
        }// ....
        return  null;
    }

   /* @Before
    public void init(){
        // 加载BeanDefinition
        // 指定路径
        String location = "springmvc.xml";
        // 加载指定路径的配置文件
        InputStream is = getResource(location);
        // 将流数据转换为格式化数据Document
        Document document = createDocument(is);

        // 按照自定义spring语义去解析文档数据，最终将解析出来的BeanDefinition存储到集合中
        parseBeanDefinitions(document.getRootElement());
    }*/
   @Before
   public void init(){
       beanFactory = new DefaultListableBeanFactory();
       XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader((DefaultListableBeanFactory) beanFactory);
       beanDefinitionReader.loadBeanDefinitions("springmvc.xml");
   }


    /**
     *
     * @param rootElement   <beans>标签
     */
    private void parseBeanDefinitions(Element rootElement) {
        List<Element> elements = rootElement.elements();
        for (Element beanElement : elements) {
            parseBeanDefinition(beanElement);
        }

    }

    private void parseBeanDefinition(Element beanElement) {
        String className = beanElement.attributeValue("class");
        if (className == null || className.equals("")){
            return ;
        }
        Class clazzType = resolveClassName(className);

        String beanName = beanElement.attributeValue("id");
        if (beanName == null || beanName.equals("")){
            beanName = clazzType.getSimpleName();
        }

        String scope = beanElement.attributeValue("scope");
        scope = scope == null || scope.equals("") ? "singleton" : scope;

        String initMethod = beanElement.attributeValue("init-method");

        BeanDefinition bd = new BeanDefinition(beanName,className);
        bd.setClazzType(clazzType);
        bd.setScope(scope);
        bd.setInitMethod(initMethod);

        List<Element> propertyList = beanElement.elements("property");
        for (Element propElement : propertyList) {
            // 解析property标签
            parsePropElement(propElement,bd);
        }

        this.beanDefinitions.put(beanName,bd);
    }

    private void parsePropElement(Element propElement, BeanDefinition bd) {
        String name = propElement.attributeValue("name");
        String value = propElement.attributeValue("value");
        String ref = propElement.attributeValue("ref");

        if((value == null || value.equals("")) && (ref == null || ref.equals(""))){
            return ;
        }
        PropertyValue pv = null ;
        if(value != null && !value.equals("")){
            TypedStringValue typedStringValue = new TypedStringValue(value);

            Class targetType = resolveType(bd.getClazzType(),name);

            typedStringValue.setTargetType(targetType);

            pv = new PropertyValue(name,typedStringValue);
        }else if(ref != null && !ref.equals("")){
            RuntimeBeanReference beanReference = new RuntimeBeanReference(ref);
            pv = new PropertyValue(name,beanReference);
        }

        bd.addPropertyValue(pv);
    }

    private Class resolveType(Class clazzType, String name) {
        try{
            Field field = clazzType.getDeclaredField(name);
            field.setAccessible(true);
            return field.getType();

        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    private Class resolveClassName(String className) {
        try {
            return Class.forName(className);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    private Document createDocument(InputStream is) {
        try {
            SAXReader reader = new SAXReader();
            return reader.read(is);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    private InputStream getResource(String location) {
        return this.getClass().getClassLoader().getResourceAsStream(location);
    }


    // 用来存储配置文件中bean标签的信息（bean的原材料信息）
    private Map<String, BeanDefinition> beanDefinitions = new HashMap<>();

    // 用来存储bean的对象
    private  Map<String,Object> beans = new HashMap<>();

    // 形成扩展性好的方法
    public Object getBean(String name){

        try {
            Object bean = beans.get(name);
            if (bean != null) {
                return bean;
            }
            BeanDefinition bd = beanDefinitions.get(name);
            if (bd == null) {
                return null;
            }
            String scope = bd.getScope();
            if ("singleton".equals(scope)){
                bean = doCreateBean(bd);

                beans.put(name,bean);
            }else if ("prototype".equals(scope)){
                bean = doCreateBean(bd);
            }

            return bean;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private Object doCreateBean(BeanDefinition bd) throws Exception{
        // 实例化（new对象）
        Object bean = createBeanInstance(bd);

        // 属性填充（依赖注入）
        populateBean(bd,bean);

        // 初始化
        initializeBean(bd,bean);

        return bean;
    }

    private void initializeBean(BeanDefinition bd, Object bean)  throws Exception{
        String initMethod = bd.getInitMethod();
        if (initMethod == null || "".equals(initMethod)){
            return;
        }
        Method method = bd.getClazzType().getDeclaredMethod(initMethod);
        if (method != null){
            method.invoke(bean);
        }
    }

    private void populateBean(BeanDefinition bd, Object bean) throws Exception{

        // 设置属性（可能会有多个属性，遍历处理）
        List<PropertyValue> propertyValues = bd.getPropertyValues();
        for (PropertyValue propertyValue : propertyValues) {
            String name = propertyValue.getName();
            Object value = propertyValue.getValue();
            Object valueToUse = handleValue(value);

            Field field = bd.getClazzType().getDeclaredField(name);
            field.setAccessible(true);
            field.set(bean,valueToUse);
        }
    }

    private Object createBeanInstance(BeanDefinition bd) throws Exception {

        Constructor<?> constructor = bd.getClazzType().getDeclaredConstructor();
        Object bean = constructor.newInstance(); //默认调用无参的构造方法

        return bean;
    }

    private Object handleValue(Object value) {
        if (value instanceof TypedStringValue){
            TypedStringValue typedStringValue = (TypedStringValue)value;
            String stringValue = typedStringValue.getValue();

            Class targetType = typedStringValue.getTargetType();
            if (targetType == String.class){
                return stringValue;
            }else if (targetType == Integer.class){
                return Integer.parseInt(stringValue);
            }// ...

        }else if (value instanceof RuntimeBeanReference){
            RuntimeBeanReference beanReference = (RuntimeBeanReference)value;
            String ref = beanReference.getRef();
            return getBean(ref);
        }
        return null;
    }
}
